[최적화] 요청 크기 줄이기 (minify, image optimization, code splitting)

프론트엔드에서 성능은 크게 로딩 성능과 렌더링 성능으로 나눠볼 수 있다. 로딩 성능은 페이지가 얼마나 빨리 보이는지, 렌더링 성능은 사용자의 인터랙션에 얼마나 빠르게 반응하는 지라고 볼 수 있다. 로딩 성능을 개선하는 방법에는 웹페이지를 불러올 때 요청하는 파일의 용량을 낮추는 방법이 있다. 이번 포스팅에서는 어떤 방법으로 로딩 성능을 최적화 할 수 있을 지 알아보려고 한다.

1. js/css minify

먼저 요청 크기를 줄이는 방법에는 js 번들 사이즈와 css 파일 사이즈를 줄이는 방법이 있다.

우리가 작성하는 코드에는 가독성을 위해 충분한 공백과 좋은 변수명이 필요하다. 하지만 이 코드가 빌드되고 실행될 때 컴퓨터에게는 이 정보가 불필요하다. 따라서 최적화를 하기 위해 이런 공백을 제거하는 minify와 난독화 하는 uglify를 하는 과정이 필요하다.

javascript

webpack 4+버전부터는 TerserPlugin이 기본적으로 내장되어 있고 minify: true 가 default 값으로 설정되어있어 자동으로 minify와 uglify를 진행하고 있다. 따라서 커스텀 옵션을 추가하지 않는 경우라면 따로 install하지 않아도 된다.

뿐만 아니라 webpack4+버전부터는 자동으로 트리쉐이킹을 해주고 있다. https://github.com/webpack-contrib/webpack-bundle-analyzer를 사용하여 번들 사이즈를 측정하면 stat size, parsed size, gzipped size를 확인할 수 있을텐데 parsed size가 tree shaking까지 한 크기이고 gzipped size가 페이지 서빙을 할 때 압축된 크기이다. 따라서 parsed 혹은 gzipped size를 보고 번들 최적화가 필요한지 판단하는 것이 좋다.

css

CSS 또한 이런 공백을 제거할 수 있는 CssMinimizerPlugin이 있다.

그리고 각 CSS 파일을 별개로 분리할 수 있는 MiniCssExtractPlugin 이 있다.

npm install css-minimizer-webpack-plugin --save-dev
npm install mini-css-extract-plugin --save-dev
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

module.exports = {
  module: {
    rules: [
      {
        test: /.s?css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
      },
    ],
  },
  optimization: {
    minimizer: [
      // For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
      // `...`,
      new CssMinimizerPlugin(),
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
}

하지만 이 때 MiniCssExtractPluginstyle-loader와 역할이 상충하기 때문에 둘 중 하나를 선택해서 사용해야 한다. style-loader<style> 태그로 DOM에 CSS를 바로 주입시켜주는 역할을 한다.

MiniCssExtractPlugin은 각 JS파일 당 CSS 파일을 별개로 추출해 생성하기 때문에 style-loader를 사용할 필요가 없어진다.

style-loader는 한 번의 요청으로 CSS 파일을 받아오지만 현재 페이지에서 불필요한 파일까지 다운 받는다는 특징이 있고 MiniCssExtractPlugin은 반대로 현재 페이지에 필요한 파일만 받는 대신에 요청을 여러번 보낸다는 특징이 있다.

공통으로 사용되는 CSS 양이 많다면 style-loader을, 특정 파일에 CSS 양이 많아진다면 MiniCssExtractPlugin 을 사용하는 것이 좋다고 생각된다.

2. image optimization

이미지 최적화에는 다양한 방법들이 있다.

webp 변환

우선 png/jpeg 형태의 이미지를 webp로 변환하는 것이다. (gif의 경우에는 webm 혹은 mp4) webP 이미지는 화질이 별로 달라지지 않으면서도 png, jpeg보다 25~30% 작기 때문에 용량이 줄어든다.

실제로 당근마켓에서도 webp 형식으로 이미지들을 관리하고 있다고 한다. (출처)

하지만 webp가 모든 브라우저에서 지원이 되지 않기 때문에 그러한 브라우저에 접근했을 때 <picture> 태그와 <source> 태그를 사용하여 처리를 해줘야 한다.

<picture>
  <source
    className={styles.heroImage}
    srcSet={heroImageWebP}
    type="image/webp"
  />
  <img className={styles.heroImage} src={heroImage} alt="hero image" />
</picture>

이렇게 하면 webp를 지원하지 않는 브라우저에서는 아래의 png 이미지를 불러오게 된다. 이 부분이 잘 적용되었는지 확인하려면 개발자도구의 Network→Rendering 에서 Disable WebP image Format을 선택하면 된다.

이미지 압축 도구

다음으로는 직접 이미지를 압축해서 넣는 방법이다. https://imageoptim.com/howto.html 에서 프로그램을 다운받아 이미지를 업로드하면 이미지를 압축할 수 있다.

스크린샷 2023-09-07 오후 8 29 05

원래 10MB 였던 파일을 7.3MB까지 압축할 수 있다.

또한, https://squoosh.app/ 에서 이미지를 업로드하면 품질 조정, 리사이징, 파일 형식 변환을 모두 할 수 있다.

웹팩에서 ImageMinimizerWebpackPlugin을 사용하여 이미지를 압축, 리사이징 하는 방법도 있다. 이 플러그인을 사용하면 위에서 수동으로 압축하는 일을 빌드할 때 자동으로 수 있다. 위에 있는 squoosh 사이트도 라이브러리화되어 install 할 수 있다.

3. code splitting

사용자가 서비스에 접근하게 되면 js 번들을 다운로드 받게 된다. 이때 이 파일 내부에는 사용자가 현재 접근한 페이지와 무관한 코드가 포함되어있을 수도 있다. 그래서 해당 번들을 다운받느라 로딩이 오래걸릴 수도 있다. 이런 문제가 생겼을 때 해결할 수 있는 방법이 바로 하나의 번들 파일을 여러개로 분리하는 code splitting이다.

리액트에서 code splitting을 하는 방법 중 하나는 lazy loading을 사용하는 것이다.

const Home = lazy(() => import('./pages/Home/Home'))
const Search = lazy(() => import('./pages/Search/Search'))
output: {
    path: path.join(__dirname, '/dist'),
    filename: '[name].bundle.[contenthash].js',
    clean: true
 },

웹팩의 output의 filename에 [name].bundle.js 로 수정하여 번들 이름을 구분할 수 있다. 만약 번들 이름을 직접 설정해주고 싶다면 lazy loading의 import문 앞에 /*webpackChunkName: ..*/ 를 추가해주는 방법이 있다.

스크린샷 2023-09-08 오후 3 02 16

이렇게 lazy loading을 하게 되면 여러개의 번들로 나눠진다. Home에 접근을 해도 Search 페이지에서 필요한 파일들은 다운받지 않는다.

스크린샷 2023-09-04 오후 8 39 36

webpack build analyzer을 통해서도 이렇게 여러개의 번들 파일로 쪼개진 것을 확인할 수 있다.

마무리

요청 크기를 줄이는 방법에는 위의 방법들 외에도 다양한 방법들이 존재한다. 무조건적으로 최적화를 하기보단 서비스의 성능을 측정해보고 필요에 의해 최적화 방법을 선택하는 것이 중요하다고 생각한다.

참고한 자료


Written by@타미
공부하고 경험한 내용을 글로 작성합니다. 지적, 보충은 언제나 환영입니다 🙂

GitHub